﻿ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WMM.Tapioca.Taxonomy;
using WMM.Tapioca.Linkbase;
using WMM.Tapioca.Instance;
using System.Xml.Linq;
using WMM.Tapioca.Utilities;
using System.IO;
using log4net.Appender;
using log4net.Core;

namespace WMM.Tapioca.Validate
{
    /// <summary>
    /// A class that validate the calculation linkbase.
    /// </summary>
    public class ValidateCalculations
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private DTS dts;
        private int calculationSatisfied;
        private int calculationNotSatisfied;

        IAppender htmlFileAppender;

        /// <summary>
        /// The constructor.
        /// </summary>
        public ValidateCalculations(DTS dtsXbrl, bool createHtml)
        {
            dts = dtsXbrl;

            if (createHtml) // when execute the suite test the createHtml is set to false.
            {
                htmlFileAppender = CreateRollingFileAppender("RollingHtmlFileAppender", Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\Tapioca\\CalculationMessages.html");
                AddAppender(log.Logger.Name, htmlFileAppender);               

                var appenders = log4net.LogManager.GetRepository().GetAppenders();
                SetAppendersLevel(Level.Error);
            }

            ValidateSummation();

            if (createHtml)
            {
                AppendToFile();
                SetAppendersLevel(Level.Info);
            }
        }


        private static void SetAppendersLevel(Level level)
        {
            var appenders = log4net.LogManager.GetRepository().GetAppenders();

            foreach (var appender in appenders.OfType<AppenderSkeleton>())
            {
                if (appender.Name == "RichTextBoxAppender")
                {
                    appender.Threshold = level;
                }

                if (appender.Name == "RollingFileAppender")
                {
                    appender.Threshold = level;
                }
            }
        }


        // Add an appender to a logger
        public static void AddAppender(string loggerName, log4net.Appender.IAppender appender)
        {
            log4net.ILog log = log4net.LogManager.GetLogger(loggerName);
            log4net.Repository.Hierarchy.Logger l = (log4net.Repository.Hierarchy.Logger)log.Logger;

            l.RemoveAppender(appender.Name);
            l.AddAppender(appender);
        }


        // Create a new file appender
        public log4net.Appender.IAppender CreateRollingFileAppender(string name, string fileName)
        {
            log4net.Appender.RollingFileAppender appender = new log4net.Appender.RollingFileAppender();
            appender.Name = name;
            appender.Encoding = Encoding.GetEncoding("iso-8859-1");
            appender.File = fileName;
            appender.AppendToFile = false;
            appender.LockingModel = new FileAppender.MinimalLock();
            appender.MaxSizeRollBackups = 1;

            log4net.Layout.PatternLayout layout = new log4net.Layout.PatternLayout();

            layout.ConversionPattern = "<tr valign=\"top\" class=\"%level\">\n" +
                                       "    <td><strong>%level</strong></td>\n" +
                                       "    <td>%property{summationItem}</td>\n" +
                                       "    <td>%message</td>\n" +
                                       "</tr>";
            
            layout.ActivateOptions();

            appender.Layout = layout;
            appender.ActivateOptions();
            appender.Threshold = Level.Info;

            return appender;
        }


        private void AppendToFile()
        {
            string currentContent = String.Empty;

            if (File.Exists(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\Tapioca\\CalculationMessages.html"))
            {
                currentContent = File.ReadAllText(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\Tapioca\\CalculationMessages.html");
            }

            StringBuilder head = new StringBuilder();

            head.AppendLine(@"<html xmlns:xs=""http://www.w3.org/2001/XMLSchema"" xmlns:xbrli=""http://www.xbrl.org/2003/instance"" xmlns:exsl=""http://exslt.org/common""><head><meta http-equiv=""Content-Type"" content=""text/html; charset=UTF-8"">");
            head.AppendLine(@"     <style type=""text/css""> body { font:normal 68% verdana,arial,helvetica;");
            head.AppendLine(@"          color:#000000; } table tr td, table tr th { font-size: 68%; } table.details tr");
            head.AppendLine(@"          th{ font-weight: bold; text-align:left; background:#a6caf0; } table.details tr");
            head.AppendLine(@"          td{ background:#eeeee0; } p { line-height:1.5em; margin-top:0.5em;");
            head.AppendLine(@"          margin-bottom:1.0em; } h1 { margin: 0px 0px 5px; font: 165%");
            head.AppendLine(@"          verdana,arial,helvetica } h2 { margin-top: 1em; margin-bottom: 0.5em; font: bold");
            head.AppendLine(@"          125% verdana,arial,helvetica } h3 { margin-bottom: 0.5em; font: bold 115%");
            head.AppendLine(@"          verdana,arial,helvetica } h4 { margin-bottom: 0.5em; font: bold 100%");
            head.AppendLine(@"          verdana,arial,helvetica }h5 { margin-bottom: 0.5em; font: bold 100%");
            head.AppendLine(@"          verdana,arial,helvetica } h6 { margin-bottom: 0.5em; font: bold 100%");
            head.AppendLine(@"          verdana,arial,helvetica } .Error { font-weight:bold; color:red; }");
            head.AppendLine(@"          .Warn { font-weight:bold; color:blue;}");
            head.AppendLine(@"          .Failure {font-weight:bold; color:purple; } .Properties { text-align:right; } .IsVisible {");
            head.AppendLine(@"          display: block; } .NotVisible { display: none; } .Expander { cursor: hand;");
            head.AppendLine(@"          font-family: Courier; } .Parent DIV { margin-Left: 30px !important; } </style>");
            head.AppendLine(@"<title>Calculation Validation</title>");
            head.AppendLine(@"  </head>");
            head.AppendLine(@"  <body>");
            head.AppendLine(@"	<h1>Calculation Validation</h1>");
            head.AppendLine(@"	<h2>" + DateTime.Now.ToString("dddd, yyyy-MM-dd HH:mm tt") + "</h2>");
            head.AppendLine(@"	<h2> Tapioca XBRL Processor Version:1.0</h2>");
         
            head.AppendLine(@"	</h2>");
            head.AppendLine(@"	<hr size=""1"">");
            head.AppendLine(@"	<h2> Summary </h2>");
            head.AppendLine(@"	<table width=""95%"" cellspacing=""2"" cellpadding=""5"" border=""0"" class=""details"">");
            head.AppendLine(@"	  <tbody><tr valign=""top"">");           
            head.AppendLine(@"		<th> Calculation Compiled </th>");
            head.AppendLine(@"		<th> Calculation Fired </th>");
            head.AppendLine(@"		<th> Calculation Satisfied </th>");
            head.AppendLine(@"		<th> Calculation Not Satisfied </th>");
            head.AppendLine(@"	   </tr>");
            head.AppendLine(@"	<tr valign=""top"" class=""Pass"">");        
            head.AppendLine(@"	<td>" + (calculationSatisfied + calculationNotSatisfied) + "</td>");
            head.AppendLine(@"	<td>" + (calculationSatisfied + calculationNotSatisfied) + "</td>");
            head.AppendLine(@"	<td>" + calculationSatisfied + "</td>");
            head.AppendLine(@"	<td>" + calculationNotSatisfied + "</td>");
            head.AppendLine(@"	</tr>");
            head.AppendLine(@"	</tbody></table>");
            head.AppendLine(@"	<hr size=""1"">");
            head.AppendLine(@"	<h2> Detailed Output</h2>");
            head.AppendLine(@"	<table width=""95%"" cellspacing=""2"" cellpadding=""5"" border=""0"" class=""details"">");
            head.AppendLine(@"	   <tbody><tr valign=""top"">");
            head.AppendLine(@"	      <th> level </th>");
            head.AppendLine(@"	      <th> Summation Item </th>");
            head.AppendLine(@"	      <th> message </th>");
            head.AppendLine(@"	   </tr>");


            StringBuilder endOfFile = new StringBuilder();
            endOfFile.AppendLine(@"	   </tbody>");
            endOfFile.AppendLine(@"     </table>");
            endOfFile.AppendLine(@"  </body>");
            endOfFile.AppendLine(@"</html>");

            string myDocumentPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            File.WriteAllText(myDocumentPath + "\\Tapioca\\CalculationMessages.html", head.ToString() + currentContent + endOfFile.ToString());
        }

 
        


        /// <summary>
        /// Validates all summation concepts found in the instance document.
        /// </summary>
        private void ValidateSummation()
        {
            foreach (NetworkGraph networkGraph in dts.NetworkGraphs)
            {
                if (networkGraph.TupleNG.Item1.Equals("calculationLink"))
                {
                    if (networkGraph.TupleNG.Item4.Equals(Constants.XBRLSummationItemArcrole))
                    {
                        ValidateSummationItem(networkGraph);
                    }

                    if (networkGraph.TupleNG.Item4.Equals(Constants.DIMENSIONALAggregatorContributorArcrole))
                    {
                        ValidateAggregatorContributor(networkGraph);
                    }
                }

            }
        }


        private void ValidateAggregatorContributor(NetworkGraph networkGraph)
        {
            List<Summation> currentAggregators = new List<Summation>();

            foreach (Summation currentAggregator in networkGraph.SummationConcepts)
            {
                currentAggregators.Add(currentAggregator);
                // Validate the main items in the instance.

                //ValidateTuple(CurrentSummation, dts.ThisInstance.Facts);
            }

            ValidateAggregator(currentAggregators, dts.Instance.Facts);
        }


        private void ValidateAggregator(List<Summation> currentAggregators, List<Fact> factList)
        {
            List<Concept> aggregatorConcepts = LocateAggregatorConcepts(currentAggregators);

            List<FactItem> aggregatorItems = LocateAggregatorItems(aggregatorConcepts, factList);

            // If the summation concept item doesn't exist, then there is no calculation
            // to perform.

            if (aggregatorItems == null)
            {
                return;
            }

            foreach (FactItem aggregatorItem in aggregatorItems)
            {
                // If the summation concept item has a "nil" value, then there is no calculation
                // to perform.

                if (aggregatorItem.IsNilSpecified)
                {
                    return;
                }

                if (aggregatorItem.PrecisionAttributeValue.Equals("0"))
                {
                    log.Warn("Validation error on summation item inconsistent due to precision 0. Name: " + aggregatorItem.Name + " Value: " + aggregatorItem.Value);
                    dts.ValidatedInstance = false;
                    return;
                }

                //Summation is not a duplicate item.

                if (IsDuplicate(aggregatorItem, factList))
                {
                    log.Warn("Validation error: summation item is duplicate. Name: " + aggregatorItem.Name + " Value: " + aggregatorItem.Value);
                    return;
                }

                double summationRoundedValue = GetRoundedValue(aggregatorItem);

                foreach (Summation currentAggregator in currentAggregators)
                {
                    double contributingTotalRoundedValue = 0;
                    var contributingItemsFound = false;

                    foreach (Locator currentContributingLocator in currentAggregator.ContributingLocators)
                    {
                        // Find the calculation arc for the given calculation link.

                        Arc contributingCalculationArc = GetArc(currentAggregator, currentContributingLocator);

                        if (contributingCalculationArc == null)
                        {
                            continue;
                        }

                        // Find the elemement for the given locator.

                        Concept currentContributingConcept = LocateConcept(currentContributingLocator);

                        if (currentContributingConcept == null)
                        {
                            continue;
                        }

                        // Find all items for the given element. If there is more than one, and at least
                        // one of them is not p-equals with at least one of the other ones, then
                        // the entire calculation validation is forfeit, according to test 397.12 in 
                        // the XBRL-CONF-CR3-2007-03-05 conformance suite.

                        List<FactItem> allMatchingItems = LocateAggregatorItems(aggregatorItem.ContextRef, currentContributingConcept, factList);

                        if (!AllItemsNotPEquals(allMatchingItems))
                        {
                            return;
                        }

                        // Find the item for the given element.

                        if (allMatchingItems.Count == 0)
                        {
                            continue;
                        }
                        else
                        {
                            foreach (FactItem currentContributingItem in allMatchingItems)
                            {
                                if (currentContributingItem.PrecisionAttributeValue.Equals("0"))
                                {
                                    log.Warn("Validation error on contributing item inconsistent due to precision 0. Name: " + currentContributingItem.Name + " Value: " + currentContributingItem.Value);
                                    dts.ValidatedInstance = false;
                                    return;
                                }

                                // Ensure that the contributing concept item is unit-equals
                                // with the summation item.
                               
                                if (!aggregatorItem.UnitEquals(currentContributingItem))
                                {
                                    log.Warn("Validation on Summation. Role of the calculationLink:" + currentAggregator.NetworkGraphSummation.TupleNG.Item2, new XbrlException("The units are not s-equal, so a calculation inconsistency MUST be signaled."));
                                    continue;
                                }


                                // I is a descendant of the parent of S (i.e. I is a sibling of S or a descendant of one of the siblings of S).
                                
                                if (!IsDescendantsSummationParent(aggregatorItem.FactElement, currentContributingItem.FactElement))
                                {
                                    log.Warn("Validation on Summation. Role of the calculationLink:" + currentAggregator.NetworkGraphSummation.TupleNG.Item2, new XbrlException("The contributing item I:" + currentContributingItem.FactElement.Name + " is not a descendant of the parent of summation Item S: " + aggregatorItem.FactElement.Name + "."));
                                    continue;
                                }                               

                                //Verify if the contributing item is not a duplicate item.

                                if (IsDuplicate(currentContributingItem, factList))
                                {
                                    log.Error("Validation error: contributing item is duplicate. Name: " + currentContributingItem.Name + " Value: " + currentContributingItem.Value);
                                    return;
                                }

                                // Ensure that the contributing concept item does not have a nil value.

                                if (currentContributingItem.IsNilSpecified)
                                {
                                    continue;
                                }

                                // If the code is still interested in including the contributing concept item
                                // in the calculation, then get its rounded value and add it to the total.
                                
                                contributingItemsFound = true;

                                double currentContributingItemRoundedValue = GetRoundedValue(currentContributingItem);

                                currentContributingItemRoundedValue = currentContributingItemRoundedValue * (double)(contributingCalculationArc.Weight);

                                contributingTotalRoundedValue += currentContributingItemRoundedValue;
                            }
                        }
                    }
                    if (contributingItemsFound)
                    {
                        contributingTotalRoundedValue = aggregatorItem.Round(contributingTotalRoundedValue);
                        if (summationRoundedValue != contributingTotalRoundedValue)
                        {
                            StringBuilder messageBuilder = new StringBuilder();
                            string stringFormat = AssemblyResources.GetName("AggregatorConceptError");
                            messageBuilder.AppendFormat(stringFormat, aggregatorItem.Name, summationRoundedValue, contributingTotalRoundedValue);
                            log.Error("Validation error on Summation. Role of the calculationLink:" + currentAggregator.NetworkGraphSummation.TupleNG.Item2, new XbrlException(messageBuilder.ToString()));
                            dts.ValidatedInstance = false;
                            calculationNotSatisfied++;
                            return;
                        }

                        log4net.GlobalContext.Properties["summationItem"] = aggregatorItem.Name + " context:" + aggregatorItem.ContextRefName + "Aggregator:" + currentAggregator.SummationLocator.HrefResourceId;
                        log.Info(string.Format("Aggregator Item Total:{0} and Contributing items Total is: {1}", summationRoundedValue, contributingTotalRoundedValue));
                        calculationSatisfied++;
                    }
                }
            }
        }

        private List<FactItem> LocateAggregatorItems(Context contextRef, Concept currentContributingConcept, List<Fact> factList)
        {
            var itemList = new List<FactItem>();

            foreach (Fact currentFact in factList)
            {
                if ((currentFact is FactItem))
                {
                    var currentItem = currentFact as FactItem;

                    foreach (XElement explicitMember in currentItem.ContextRef.ExplicitMembers)
                    {
                        string valueQN = explicitMember.Value;
                        XNamespace ns = explicitMember.GetNamespaceOfPrefix(GetPrefix(valueQN));
                        XName value = ns + GetLocalName(valueQN);

                        if (currentContributingConcept.Name.Equals(value.LocalName) && 
                            XNode.DeepEquals(currentItem.ContextRef.PeriodElement, contextRef.PeriodElement))
                            {
                                itemList.Add(currentItem);
                            }
                    }                        
                }
            }

            return itemList;
        }


        private List<Concept> LocateAggregatorConcepts(List<Summation> currentAggregators)
        {
            List<Concept> aggregatorConcepts = new List<Concept>();

            foreach (Summation aggregator in currentAggregators)
            {
                Concept aggregatorConcept = LocateConcept(aggregator.SummationLocator);
                if (aggregatorConcept != null)
                {
                    aggregatorConcepts.Add(aggregatorConcept);
                }
            }
            return aggregatorConcepts;
        }


        private List<FactItem> LocateAggregatorItems(List<Concept> aggregatorConcepts, List<Fact> factList)
        {
            var itemList = new List<FactItem>();
            bool found = true;            

                foreach (Fact currentFact in factList)
                {
                    if ((currentFact is FactItem))
                    {
                        var currentItem = currentFact as FactItem;

                        foreach (XElement explicitMember in currentItem.ContextRef.ExplicitMembers)
                        {
                            string valueQN = explicitMember.Value;
                            XNamespace ns = explicitMember.GetNamespaceOfPrefix(GetPrefix(valueQN));
                            XName value = ns + GetLocalName(valueQN);

                            foreach (Concept aggregatorConcept in aggregatorConcepts)
                            {
                                if (aggregatorConcept.Name.Equals(value.LocalName))
                                {
                                    goto nextExplicitMember;
                                }
                            }
                            found = false;

                        nextExplicitMember:;
                        }

                        if (found)
                            itemList.Add(currentItem);
                    }
                    //else if ((CurrentFact is WMM.Tapioca.Instance.Tuple))
                    //{
                    //    var currentTuple = CurrentFact as WMM.Tapioca.Instance.Tuple;
                    //    var tupleList = LocateItems(concept, currentTuple.Facts);
                    //    ItemList.AddRange(tupleList);
                    //}
                }

            return itemList;
        }

        private string GetLocalName(string dimensionQN)
        {
            string[] dimAttrQN = dimensionQN.Split(':');

            if (dimAttrQN.Count() > 1)
            {
                return dimAttrQN[1];
            }
            else
            {
                return dimAttrQN[0];
            }

        }

        private string GetPrefix(string dimensionQN)
        {
            string[] dimAttrQN = dimensionQN.Split(':');

            if (dimAttrQN.Count() > 1)
            {
                return dimAttrQN[0];
            }
            else
            {
                return "";
            }

        }

        /// <summary>
        /// Validates all summation concepts found in the current calculation link.
        /// </summary>
        /// <param name="networkGraph">
        /// The calculation link whose containing summation concepts should be validated.
        /// </param>
        private void ValidateSummationItem(NetworkGraph networkGraph)
        {
            foreach (Summation currentSummation in networkGraph.SummationConcepts)
            {
                // Validate the main items in the fragment.
                Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating Summation: " + currentSummation.SummationLocator.HrefResourceId));
                ValidateSummation(currentSummation, dts.Instance.Facts);

                ValidateTuple(currentSummation, dts.Instance.Facts);
            }
        }


        private void ValidateTuple(Summation currentSummation, List<Fact> factList)
        {
            // Look for any tuples in the fragment and validate their items as well. This action

            foreach (Fact currentFact in factList)
            {
                if (currentFact is WMM.Tapioca.Instance.FactTuple)
                {
                    var currentTuple = currentFact as WMM.Tapioca.Instance.FactTuple;
                    ValidateSummation(currentSummation, currentTuple.Facts);
                    ValidateTuple(currentSummation, currentTuple.Facts);
                }
            }
        }

        /// <summary>
        /// Validates a given summation concept.
        /// </summary>
        /// <param name="currentSummation">
        /// The summation concept to be validated.
        /// </param>
        /// <param name="factList">
        /// The collection of items that should be searched when looking for summation or contributing items.
        /// </param>
        private void ValidateSummation(Summation currentSummation, List<Fact> factList)
        {
            Concept summationConcept = LocateConcept(currentSummation.SummationLocator);

            //FactItem summationItem = LocateItem(summationConcept, factList);

            List<FactItem> summationItems = dts.LocateItems(summationConcept, factList);

            foreach (FactItem summationItem in summationItems)
            {
                // If the summation concept item doesn't exist, then there is no calculation
                // to perform.

                if (summationItem == null)
                {
                    return;
                }

                // If the summation concept item has a "nil" value, then there is no calculation
                // to perform.

                if (summationItem.IsNilSpecified)
                {
                    return;
                }

                // If any item of the calculation has a precision attribute value 0 then the binding calculation is deemed to be inconsistent.

                if (summationItem.PrecisionAttributeValue.Equals("0"))
                {
                    log.Warn("The summation item is inconsistent due to precision 0. Name: " + summationItem.Name + " Value: " + summationItem.Value);
                    dts.ValidatedInstance = false;
                    return;
                }

                //Summation is not a duplicate item.

                if (IsDuplicate(summationItem, factList))
                {
                    log.Warn("Warn: summation item is duplicate. Name: " + summationItem.Name + " Value: " + summationItem.Value);
                    return;
                }

                double summationRoundedValue = GetRoundedValue(summationItem);
                double contributingTotalRoundedValue = 0;
                bool isContributingItemsFound = false;

                foreach (Locator currentContributingLocator in currentSummation.ContributingLocators)
                {
                    // Find the calculation arc for the given calculation link.

                    Arc contributingCalculationArc = GetArc(currentSummation, currentContributingLocator);

                    if (contributingCalculationArc == null)
                    {
                        continue;
                    }

                    // Find the elemement for the given locator.

                    Concept currentContributingConcept = LocateConcept(currentContributingLocator);

                    if (currentContributingConcept == null)
                    {
                        continue;
                    }

                    // Find all items for the given element. If there is more than one, and at least
                    // one of them is not p-equals with at least one of the other ones, then
                    // the entire calculation validation is forfeit, according to test 397.12 in 
                    // the XBRL-CONF-CR3-2007-03-05 conformance suite.

                    List<FactItem> currentContributingItems = dts.LocateItems(currentContributingConcept, factList);

                    List<Fact> facts = GetFacts(currentContributingItems);

                    foreach (FactItem currentContributingItem in currentContributingItems)
                    {
                        if (currentContributingItem.PrecisionAttributeValue.Equals("0"))
                        {
                            log.Warn("The contributing item is inconsistent due to precision 0. Name: " + currentContributingItem.Name + " Value: " + currentContributingItem.Value);
                            dts.ValidatedInstance = false;
                            return;
                        }
                        // Ensure that the contributing concept item is context-equals
                        // with the summation item.


                        if (!summationItem.ContextEquals(currentContributingItem))
                        {
                            //log.Info("Role:" + currentSummation.NetworkGraphSummation.TupleNG.Item2 + " The contexts are not s-equal, so a calculation inconsistency MUST be signaled. summationItem: " + summationItem.Name + " currentContributingItem: " + currentContributingItem.Name);
                            continue;
                        }


                        // Ensure that the contributing concept item is unit-equals
                        // with the summation item.


                        if (!summationItem.UnitEquals(currentContributingItem))
                        {
                            log.Warn("Role of the calculationLink:" + currentSummation.NetworkGraphSummation.TupleNG.Item2, new XbrlException("The units are not s-equal, so a calculation inconsistency MUST be signaled."));
                            continue;
                        }


                        // I is a descendant of the parent of S (i.e. I is a sibling of S or a descendant of one of the siblings of S).


                        if (!IsDescendantsSummationParent(summationItem.FactElement, currentContributingItem.FactElement))
                        {
                            log.Warn("Role of the calculationLink:" + currentSummation.NetworkGraphSummation.TupleNG.Item2, new XbrlException("The contributing item I:" + currentContributingItem.FactElement.Name + " is not a descendant of the parent of summation Item S: " + summationItem.FactElement.Name + "."));
                            continue;
                        }


                        //Verify if the contributing item is not a duplicate item.

                        if (IsDuplicate(currentContributingItem, facts))
                        {
                            log.Warn("Warn: Contributing item is duplicate. Name: " + currentContributingItem.Name + " Value: " + currentContributingItem.Value);
                            return;
                        }

                        // Ensure that the contributing concept item does not have a nil value.

                        if (currentContributingItem.IsNilSpecified)
                        {
                            continue;
                        }

                        // If the code is still interested in including the contributing concept item
                        // in the calculation, then get its rounded value and add it to the total.

                        isContributingItemsFound = true;

                        double currentContributingItemRoundedValue = GetRoundedValue(currentContributingItem);

                        currentContributingItemRoundedValue = currentContributingItemRoundedValue * (double)(contributingCalculationArc.Weight);

                        contributingTotalRoundedValue += currentContributingItemRoundedValue;
                    }
                }

                if (isContributingItemsFound)
                {
                    contributingTotalRoundedValue = summationItem.Round(contributingTotalRoundedValue);

                    if (summationRoundedValue != contributingTotalRoundedValue)
                    {
                        log4net.GlobalContext.Properties["summationItem"] = summationItem.Name + " (context: " + summationItem.ContextRefName + ")";
                        log.Error(string.Format("SummationItem Total :{0} and Contributing items Total is: {1}", summationRoundedValue, contributingTotalRoundedValue));
                        dts.ValidatedInstance = false;
                        calculationNotSatisfied++;
                        return;
                    }

                    log4net.GlobalContext.Properties["summationItem"] = summationItem.Name + " (context: " + summationItem.ContextRefName + ")";
                    log.Info(string.Format("SummationItem Total :{0} and Contributing items Total is: {1}", summationRoundedValue, contributingTotalRoundedValue));
                    calculationSatisfied++;
                }
            }
        }


        private List<Fact> GetFacts(List<FactItem> currentContributingItems)
        {
            List<Fact> facts = new List<Fact>();

            foreach (FactItem currentContributingItem in currentContributingItems)
            {
                facts.Add(currentContributingItem);
            }

            return facts;
        }


        /// <remarks>>
        /// S is not a duplicate item (as defined in section 4.10), and None of the contributing items are duplicates.
        /// Item X and item Y are duplicates if and only if all the following conditions apply:
        /// 
        /// i.            X is not identical to Y, and
        /// ii.           the element local name of X is s-equal to the element local name of Y, and
        /// iii.          X and Y are defined in the same namespace, and
        /// iv.           X is p-equal to Y, and
        /// v.            X is c-equal to Y , and
        /// vi.           X is u-equal to Y.
        /// </remarks>
        private bool IsDuplicate(FactItem calculationItem, List<Fact> factList)
        {
            foreach (Fact currentFact in factList)
            {
                if (currentFact is FactItem)
                {
                    FactItem currentItem = currentFact as FactItem;

                    if (Object.ReferenceEquals(calculationItem, currentItem))
                    {
                        continue;
                    }

                    if (calculationItem.Name.Equals(currentItem.Name) &&
                        calculationItem.Namespace.Equals(currentItem.Namespace))
                    {
                        if (calculationItem.ParentEquals(currentItem) &&
                            calculationItem.ContextEquals(currentItem) &&
                            calculationItem.UnitEquals(currentItem))
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }


        private bool IsDescendantsSummationParent(XElement summationItem, XElement contributingItem)
        {
            foreach (XElement descendant in summationItem.Parent.Descendants())
            {
                if (descendant == contributingItem)
                {
                    return true;
                }
            }

            return false;
        }


        private double GetRoundedValue(FactItem itemToRounded)
        {
            double roundedValue = Convert.ToDouble(itemToRounded.Value);

            if (itemToRounded.IsInfiniteDecimals)
            {
                return roundedValue;
            }

            if (itemToRounded.Decimals >= 0)
            {
                roundedValue = Math.Round(roundedValue, itemToRounded.Decimals);
            }
            else
            {
                double powerOfTen = Math.Pow(10.0, (double)(Math.Abs(itemToRounded.Decimals)));
                roundedValue = roundedValue / powerOfTen;
                roundedValue = Math.Round(roundedValue);
                roundedValue = roundedValue * powerOfTen;
            }

            return roundedValue;
        }


        /// <summary>
        /// Find the arc that is referenced by the given locator.
        /// </summary>
        /// <remarks>
        /// The "to" link is searched.
        /// </remarks>
        /// <param name="currentSummation">The locator used as the source of the search.</param>
        /// /// <param name="contributingLocator">The locator used as the source of the search.</param>
        /// <returns>The Arc referenced by the Locator, or null if a arc cannot be found.</returns>
        internal Arc GetArc(Summation currentSummation, Locator contributingLocator)
        {
            foreach (Arc currentArc in currentSummation.SummationLocator.OutArcs)
            {
                if (currentArc.ToId.Equals(contributingLocator.Label))
                {
                    return currentArc;
                }
            }

            return null;
        }


        /// <summary>
        /// Locates an item in the fragment's list of facts.
        /// </summary>
        /// <param name="concept">
        /// A schema element defining the item to be found.
        /// </param>
        /// <returns>
        /// A reference to the first item that matches the given element, or null if no matching item is found.
        /// </returns>       
        private FactItem LocateItem(Concept concept)
        {
            return LocateItem(concept, dts.Instance.Facts);
        }


        /// <summary>
        /// Locates an item in a list of facts.
        /// </summary>
        /// <param name="concept">
        /// A schema element defining the item to be found.
        /// </param>
        /// <param name="factList">
        /// The collection of items that should be searched.
        /// </param>
        /// <returns>
        /// A reference to the first item that matches the given element, or null if no matching item is found.
        /// </returns>
        private FactItem LocateItem(Concept concept, List<Fact> factList)
        {
            if (concept == null)
            {
                return null;
            }

            foreach (Fact currentFact in factList)
            {
                var currentItem = currentFact as FactItem;

                if (currentItem != null)
                {
                    if (currentItem.Concept.Equals(concept))
                    {
                        return currentItem;
                    }
                }
            }

            return null;
        }


        /// <summary>
        /// Locates an element given an element locator.
        /// </summary>
        /// <param name="elementLocator">
        /// The locator specifying the element to find.
        /// </param>
        /// <returns>
        /// The element referenced by the locator; null if the element cannot be found.
        /// </returns>        
        private Concept LocateConcept(Locator elementLocator)
        {
            foreach (TaxonomySchema currentSchema in this.dts.TaxonomySchemas)
            {
                var foundElement = currentSchema.LocateConcept(elementLocator);

                if (foundElement != null)
                {
                    return foundElement;
                }
            }

            return null;
        }


        /// <summary>
        /// Checks a list of <see cref="FactItem"/> objects to see if any of them are p-equals.
        /// </summary>
        /// <param name="itemList">
        /// A collection of items to be checked.
        /// </param>
        /// <returns>
        /// True is returned if all of the items in the supplied collection are not p-equals.
        /// False is returned if any two items in the supplied collection are p-equals.
        /// </returns>        
        private bool AllItemsNotPEquals(List<FactItem> itemList)
        {
            if (itemList.Count < 2)
            {
                return true;
            }
            
            for (var itemIndex1 = 0; itemIndex1 < itemList.Count; itemIndex1++)
            {
                var item1 = itemList[itemIndex1];

                for (var itemIndex2 = itemIndex1 + 1; itemIndex2 < itemList.Count; itemIndex2++)
                {
                    var item2 = itemList[itemIndex2];

                    if (item1.ParentEquals(item2))
                    {
                        return false;
                    }
                }
            }

            return true;
        }
    }
}
